/*
	Copyright (C) 2010 Salil Bhagurkar

	This file is part of k3

	k3 is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	k3 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with k3.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <shared.h>

static struct fs *sched_active =  NULL, *sched_waiting = NULL;
//Global pointer that points to the current task
volatile struct task *current = NULL;


/*Perform a switch between tasks. This is a simple wrapper to the asm functions responsible to dispatch a new and an old task*/
static int sched_switch_task(struct task *next, struct task *prev)
{
	if(next->first_run) {
		next->first_run = 0;
		arch_switch_task_first(next->stack_ptr, &prev->stack_ptr);
	} else {
		arch_switch_task(next->stack_ptr, &prev->stack_ptr);
	}
	return 0;
}

/*
Get next task from the current task ptr
*/
static int sched_rr_get_next(struct fs **cur)
{
	int e;
	e = fsiter_get_next(cur);
	if(e) {
		*cur = sched_active->links;
		e = fsiter_get_next(cur);
		if(e)
			*cur = sched_active->links;
	}
	return 0;
}

/*
Schedule to the next task. Tasks are present in /sched/active. Finds the next task in round robin
form and then switches to it
*/
static int sched_schedule(struct fs *referrer, struct fs *this, struct buffer *buf, u32 offset, u32 count)
{
	struct fs *prev_fs, *next_fs;
	struct task *prev, *next;

	prev_fs = current->fs;
	next_fs = prev_fs;

	sched_rr_get_next(&next_fs); //get next task to run

	current = (struct task *)next_fs->buf.data;
	next = (struct task *)next_fs->buf.data;
	prev = (struct task *)prev_fs->buf.data;

	dprint(D_FREQ, "", "Switch from %s to %s", prev_fs->name, next_fs->name);

	//Schedule if the tasks are not equal
	if(next != prev)
		sched_switch_task(next, prev);
}


/*This function allocates a 4kB stack for a process. The pointer is set at the end of the stack with an extra safety space beyond it*/
static int sched_stack_malloc(void **stack_ptr)
{
	*(stack_ptr) = malloc_get(0x4100, "proc");
	if(!(*stack_ptr))
		return EMEMERR;
	(*(stack_ptr))+=0x4000;
	return 0;
}

static int sched_init_task(struct task *task, struct fs *task_fs)
{
	dprint(D_INFO, task_fs->name, "Initialize task");
	task_fs->buf.data = task;
	task_fs->buf.len = sizeof(struct task);
	if(sched_stack_malloc(&task->stack))
		return EMEMERR;
	task->stack_ptr = task->stack;
	task->stack_ptr = arch_prepare_task(task->stack_ptr, task_fs, task->run); //Prepare a proper stack pointer for the task
	task->fs = task_fs;
	task->first_run = 1;
}

static int sched_notify_new_task_added(struct fs *this, struct fs *child)
{
	struct fs *new;
	//Move to active list
	fs_copy_get_new(sched_active, child, &new);
	sched_init_task((struct task *)new->buf.data, new);
	return 0;
}

/*This is the task that is already running. We need to switch from this to others and then store the context into main_task. While
swithing back the context is restored from main_task*/
static struct task main_task;
static struct fs main_task_fs = {.name = "main", FS_INIT};


static struct task idle_task;
static struct fs idle_task_fs = {.name = "idle", FS_INIT};


static int sched_idle_run(struct fs *this)
{
	dprint(D_PROG, this->name, "Started");
	sched_switch_task(&main_task, &idle_task);
	while(1) {
		arch_halt();
	}
	return 0;
}

/*This needs separate initiation, since it's already running*/
static void sched_init_main_task()
{
	dprint(D_PROG, "main", "Initialize");
	main_task.first_run = 0;
	main_task_fs.buf.data = &main_task;
	main_task_fs.buf.len = 1;
	main_task.fs = &main_task_fs;
}

int sched_init()
{

	dprint(D_PROG, "sched", "Initialize");
	struct fs *sched;
	sched = fs_create(FSFLAGS_DEVICE, "sched");
	sched->system = fs_create_system(0, dummy, sched_schedule, dummy, sched);
	fs_link(fsparse_get("/"), sched);

	sched_active = fs_create(FSFLAGS_DEVICE, "active");
	sched_waiting = fs_create(FSFLAGS_DEVICE, "waiting");

	fs_link(sched, sched_active);
	fs_link(sched, sched_waiting);

	idle_task.run = sched_idle_run;
	sched_init_task(&idle_task, &idle_task_fs);
	fs_link(sched_active, &idle_task_fs);


	sched_init_main_task();
	fs_link(sched_active, &main_task_fs);


	current = &main_task;


	//Check if we can schedule to the idle task. Idle task schedules
	//back to main task
	sched_switch_task(&idle_task, &main_task);

	init_sched_arch(); //Setup some shit related to the scheduler

	//Start new tasks when added
	fsres_add_listener(sched, sched, sched_notify_new_task_added);

	return 0;
}
